// Copyright (C) 2008 Taylor L. Riche <riche@cs.utexas.edu>
//  
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software 
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//

/**
 * 
 */
package core;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.List;
import java.util.ArrayList;
import java.util.Hashtable;
import syscomp.entities.*;
import appcomp.entities.*;

/**
 * @author riche
 *
 */
public class CodeGenerator {
	
	private Hashtable<String, Entity> entityDictionary = new Hashtable<String, Entity>(); 
	
	public void generateProfiler(String appName, String sysName, ArrayList<Operator> opList) 
	{
		String filename = "lc_prof_main.cc";
		FileOutputStream out = null;
		PrintStream file = null;
		try {
		    out = new FileOutputStream(filename);
		    file = new PrintStream(out);
		}
		catch (FileNotFoundException e) {
		    System.err.println("Specified file " + filename + " does not exist.");
		    System.exit(1);
		}
		
		file.println("#include <iostream>");
		file.println("#include <fstream>");
		file.println("#include \"lc_" + appName + "_Application.h\"");
		file.println("#include <sysmodel/lc_" + sysName + "_System.h>");
		file.println();
		file.println("int");
		file.println("main(int argc, int ** argv)");
		file.println("{");
		file.println();

		file.println("\t" + appName + " myApp;");
		file.println("\t" + sysName + " mySys;");
		file.println();

		// setup system and app
		file.println("\tmyApp.setupProfiler(&mySys);");
		
		// open xml file
		file.println("\tstd::ofstream outfile;");
		file.println("\toutfile.open(\"" + appName + "_profData.xml\", std::ios::out);");
		file.println("\toutfile << \"<prof>\" << std::endl;");
		
		file.println("\tlagniappe::ProcElement * tempProc = NULL;");
		
		for (Operator o : opList) {
			if(!o.doesGenerate()) {
//				file.println("\ttempProc = mySys.createProfilerResources();");
//				file.println("\ttempProc->init();");
				file.println("\tstatic_cast<lagniappe::Operator *>(myApp.lookupOperator(\"" + o.getName() + "\"))->profile(&mySys);");
//				file.println("\tmySys.destroyProfilerResources();");
				file.println("\tstatic_cast<lagniappe::Operator *>(myApp.lookupOperator(\"" + o.getName() + "\"))->writeProfileInfo(outfile);");
			}
		}

		file.println("\toutfile << \"</prof>\" << std::endl;");
		
		file.println("\toutfile.close();");
		file.println("\texit(0);");
		file.println("}");

	}
	
	public void generateMain(String appName, String sysName)
	{
		String filename = "lc_main.cc";
		FileOutputStream out = null;
		PrintStream file = null;
		try {
		    out = new FileOutputStream(filename);
		    file = new PrintStream(out);
		}
		catch (FileNotFoundException e) {
		    System.err.println("Specified file " + filename + " does not exist.");
		    System.exit(1);
		}

		file.println("#include \"lc_" + appName + "_Application.h\"");
		file.println("#include <sysmodel/lc_" + sysName + "_System.h>");
		file.println("#include \"cmdline.h\"");
		file.println();
		file.println("int");
		file.println("main(int argc, char * const * argv)");
		file.println("{");
		file.println();
		file.println("\tgengetopt_args_info args_info;");
		file.println("\t" + appName + " myApp;");
		file.println("\t" + sysName + " mySys;");
		file.println();
		file.println("\tif(cmdline_parser(argc, argv, &args_info) != 0) exit(1);");
		
		file.println("\tmyApp.setup(&mySys, &args_info);");
		file.println("\tmyApp.startLoop(&mySys);");
		file.println();

		file.println("\texit(0);");
		file.println("}");

	}
	
	public void generateApplication(String name, String init, ArrayList<Operator> opList, ArrayList<Connector> conList) {
		// Create main application builder
		String filename = "lc_" + name + "_Application.h";
		FileOutputStream out = null;
		PrintStream file = null;
		try {
		    out = new FileOutputStream(filename);
		    file = new PrintStream(out);
		}
		catch (FileNotFoundException e) {
		    System.err.println("Specified file " + filename + " does not exist.");
		    System.exit(1);
		}
		
		//guards
		file.println("#ifndef LC_" + name.toUpperCase() + "_H_");
		file.println("#define LC_" + name.toUpperCase() + "_H_");
		file.println();

		
		// std includes
		file.println("#include <map>");
		file.println("#include <utility>");
		file.println("#include <lagniappe/Application.h>");
		
		// Include operators
		for(Operator o: opList) {
			o.generateIncludes(file);
		}
		
		file.println();
		file.println("class " + name + " : public lagniappe::Application {");
		file.println();
		
		if(init != null) {
			file.println("private:");
			file.println("\tvoid " + init + "();");
		}
		
		file.println();
		file.println("public:");
		file.println("\tvirtual void buildOperators(lagniappe::System * sys, void * arg_struct, bool prof = false) {");
		
		
		if(init != null) {
			file.println("\t\t" + init + "();");
		}
		
		file.println("\t\topVec.resize(" + opList.size() + ");");
		
		// Instantiate instances of all user defined operators
		for(Operator o: opList) {
			o.generateFrameworkCode(file);			
		}
		
		file.println("\t}");
		
		file.println();
		file.println("\tvirtual void connectGraph() {");
		file.println("\t\tstd::pair<lagniappe::CoreOp *, uint32_t> * tempPair;");
		for(Connector c : conList) {
			c.generateFrameworkCode(file);
		}
		file.println("\t}");
		
		// schedule
		// do a dynamic cast to prevent from scheduling system operators (request devices)
		
		file.println("\tvirtual void schedule(lagniappe::System * sys) {");
		file.println("\t\tfor(std::vector<lagniappe::CoreOp *>::iterator iter = opVec.begin();");
		file.println("\t\t\titer != opVec.end(); iter++) {");
		file.println("\t\t\tlagniappe::Operator * oper = dynamic_cast<lagniappe::Operator *>(*iter);");
		file.println("\t\t\tif(oper != NULL) {");
		file.println("\t\t\t\tlagniappe::ProcElement * tempProc = sys->getFreeProc(oper, lagniappe::INITIAL);");
		file.println("\t\t\t\tif(tempProc == NULL) tempProc = sys->roundRobin();");
		file.println("\t\t\t\toper->schedule(tempProc, true);");
		file.println("\t\t\t\tlagniappe::CommChannel * tempChan = tempProc->getSrcChan();");
		file.println("\t\t\t\toper->registerPorts(tempChan);");

		file.println("\t\t\t}");
		file.println("\t\t}");
		
		// ping the request devices that they can start receiving packets
		file.println("\t\tfor(std::vector<lagniappe::CoreOp *>::iterator iter = opVec.begin();");
		file.println("\t\t\titer != opVec.end(); iter++) {");
		file.println("\t\t\tlagniappe::RequestDevice * oper = dynamic_cast<lagniappe::RequestDevice *>(*iter);");
		file.println("\t\t\tif(oper != NULL) {");
		file.println("\t\t\t\toper->start();");
		file.println("\t\t\t}");
		file.println("\t\t}");

		file.println("\t}");
		
		file.println();
		file.println("};");
		
		//close guard
		file.println("#endif");
	}

	public void generateEntities(List<? extends Entity> entityList) {
		for(Entity e: entityList) {
			e.generateCode(this);			
		}
	}
	
	public void registerEntities(List<? extends Entity> entityList) {
		for(Entity e: entityList) {
			this.registerEntity(e);
		}
	}
	
	protected void registerEntity(Entity e) {
			entityDictionary.put(e.getName(), e);
	}

	public Entity lookupName(String name) {
		return entityDictionary.get(name);
	}
	
	public void generateSystem(String name, ArrayList<ProcessingElement> procList, 
			ArrayList<CommChannel> chanList, ArrayList<Memory> memList, ArrayList<Mutex> mtxList, ArrayList<MemGroup> memGrpList) {
		String filename = "lc_" + name + "_System.h";
		FileOutputStream out = null;
		PrintStream file = null;
		try {
		    out = new FileOutputStream(filename);
		    file = new PrintStream(out);
		}
		catch (FileNotFoundException e) {
		    System.err.println("Specified file " + filename + " does not exist.");
		    System.exit(1);
		}
		
		// guards
		file.println("#ifndef LC_" + name.toUpperCase() + "_H_");
		file.println("#define LC_" + name.toUpperCase() + "_H_");
		file.println();

		
		// std includes
		file.println("#include <map>");
		file.println("#include <utility>");
		file.println("#include <lagniappe/System.h>");
		
		// print includes
		for(ProcessingElement p: procList) {
			p.generateIncludes(file);
		}
		for(Memory m : memList) {
			m.generateIncludes(file);
		}
		for(CommChannel c : chanList) {
			c.generateIncludes(file);
		}
		for(Mutex mtx: mtxList) {
			mtx.generateIncludes(file);
		}
		

		
		file.println();
		file.println("class " + name + " : public lagniappe::System {");
		file.println();
		
		file.println("public:");
		
		file.println("\tvirtual lagniappe::ProcElement * createProfilerResources() {");
		
		// use first processor in list 
		ProcessingElement p_profile = procList.get(0);
		file.println("\t\tprocMap[\"" + p_profile.getName() + "\"] = new " + p_profile.getName() + "(true);");
		file.println("\t\treturn procMap[\"" + p_profile.getName() + "\"];");
		
		file.println("\t}");
		
		file.println("\tvirtual void destroyProfilerResources() {");
		file.println("\t\tdelete procMap[\"" + p_profile.getName() + "\"];");
		file.println("\t}");
		
		file.println("\tvirtual void createResources() {");
		
		file.println("\t\tlagniappe::ProcElement * tempProc;");
		file.println("\t\tlagniappe::CommChannel * tempChan;");
		
		// create the channels
		for (CommChannel c : chanList) {
			file.println("\t\tchanMap[\"" + c.getName() + "\"] = new " + c.getName() + ";");
		}
		
		// create procs and assign a channel to each proc
		for (ProcessingElement p : procList) {
			file.println("\t\ttempProc = new " + p.getName() + ";");
			file.println("\t\tprocMap[\"" + p.getName() + "\"] = tempProc;");
			file.println("\t\taddFreeProc(tempProc);");
			file.println("\t\ttempProc->setSrcChan(chanMap[\"" + p.getSrcChannel() + "\"]);");
		}
		
		// set up channel network
		for (ProcessingElement p : procList) {
			file.println("\t\ttempProc = procMap[\"" + p.getName() + "\"];");
			for(String s : p.getChanList()) {
				file.println("\t\ttempChan = chanMap[\"" + s + "\"];");
				CommChannel temp = (CommChannel)this.entityDictionary.get(s);
				file.println("\t\ttempProc->addDestChan(procMap[\"" + temp.getSinkProc() + "\"], tempChan);");
			}
		}
		
		// create and populate the memgroups
		file.println("\t\tlagniappe::MemGroup * tempMG = NULL;");
		for (MemGroup mg : memGrpList) {
			file.println("\t\tmemGroupList.push_back(tempMG = new lagniappe::MemGroup());");
			mg.generateFrameworkCode(file);
		}

		// fire up the threads
		file.println("\t\tfor(std::map<std::string, lagniappe::ProcElement *>::iterator iter = procMap.begin();");
		file.println("\t\t\titer != procMap.end(); iter++) {");
		file.println("\t\t\titer->second->init();");
		file.println("\t\t}");
		file.println("\tprocIter = procMap.begin();");
		file.println("\t}");
		
		file.println("\tvirtual lagniappe::Mutex * getNewMutex() {");
		// TODO currently assuming just one mutex implementation... this is obviously not the right way to do it
		file.println("\t\treturn new " + mtxList.get(0).getName() + "();");
		file.println("\t}");
		
//		file.println("\tvirtual lagniappe::ProcElement * getFreeProc(lagniappe::Operator * o)");
//		file.println("\t\t{return NULL;}");
		
		file.println("};");
		
		file.println("#endif");
	}
}
